"""
演示一个简单的虚拟拖拽
步骤：
1、opencv 读取视频流
2、在视频图像上画一个方块
3、通过mediapipe库获取手指关节坐标
4、判断手指是否在方块上
5、是，方块跟着移动
6、完善：通过食指和中指指尖距离确定是否激活移动
7、完善：画面显示FPS等信息
"""
 
# 1导入opencv
import cv2
import numpy as np
import math
 
# 10 导入mediapipe：https://google.github.io/mediapipe/solutions/hands
import mediapipe as mp
 
# 11 mediapipe相关初始化
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands

# 12 Hands 参数：
# static_image_mode 表示 静态图像还是连续帧视频；
# max_num_hands 表示 最多识别多少只手，手越多识别越慢；
# min_detection_confidence 表示 检测置信度阈值；
# min_tracking_confidence 表示 各帧之间跟踪置信度阈值；
hands = mp_hands.Hands(
    model_complexity=0,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
 
# 2读取视频流
cap = cv2.VideoCapture(0)
 
# 3获取画面宽度、高度
width = 800  # 宽度  
height = 600  # 高度
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)  
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)  
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(width,height)
 
# 方块初始数组
square_x = 100
square_y = 100
w = 200
h = 200
 
L1 = 0
L2 = 0

on_square = False
square_color = (0,255,0)
 
while True:
    # 4读取每一帧
    ret,frame = cap.read()
    
    # 5对图像进行处理
    # 6 翻转图像 arg2:1 垂直翻转 镜像 
    frame = cv2.flip(frame,1)

    # 13 颜色转换
    frame.flags.writeable = False
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # 14 mediapipe处理 识别
    results = hands.process(frame)
 
    # 15 颜色转换回来
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
 
     
    # 16 multi_hand_landmarks是侦测到的手的集合
    # 17判断是否出现手
    if results.multi_hand_landmarks:
        # 18解析遍历双手
        for hand_landmarks in results.multi_hand_landmarks:
            # 19绘制21个关键点
            # mp_drawing.draw_landmarks(
            #     frame,
            #     hand_landmarks,
            #     mp_hands.HAND_CONNECTIONS,
            #     mp_drawing_styles.get_default_hand_landmarks_style(),
            #     mp_drawing_styles.get_default_hand_connections_style())

# and_landmarks 对象通常包含了每个检测到的手部关键点的三维坐标 x, y, z 
# 在大多数情况下使用二维图像输入时 z坐标通常不会包含有意义的深度信息
# 这里的 x 和 y 坐标表示了关键点在二维图像平面上的位置 以像素为单位
# 而 z 坐标通常是一个辅助值 它可能由模型内部计算得出 
# 但在二维图像的应用中通常不直接使用
# 在某些情况下 z值可能反映了模型对关键点深度的某种估计 
# 但这种估计往往不够精确 尤其是在没有深度传感器的情况下。


            # 使用这两句看一下里面到底是什么？ class
            # print(type(hand_landmarks.landmark))
            # 20 dict 有21个关键点
            # print(hand_landmarks)
            # exit()
 
            # 21   21个关键点的x,y坐标列表
            #   hand_landmarks.landmark 类的迭代器__next__
            x_list = []
            y_list = []
            for landmark in hand_landmarks.landmark:
                x_list.append(landmark.x)
                y_list.append(landmark.y)
             
            # 输出一下长度
            # print(len(x_list))
 
            # 22获取食指指尖坐标，坐标位置查看：https://google.github.io/mediapipe/solutions/hands
            index_finger_x = int(x_list[8] * width)
            index_finger_y = int(y_list[8] * height)
 
            # 23获取中指坐标
            middle_finger_x = int(x_list[12] * width)
            middle_finger_y = int(y_list[12] * height)
 
            # 24计算两指距离
            # 这两个表达式在功能上完全相同 但math.hypot提供了更简洁的代码
            # 同时也避免了显式地进行平方和开方操作
            # 这有助于减少潜在的数值误差
            # 在实践中，当需要计算二维或三维空间中的两点距离时
            # 使用 math.hypot 通常是一个更好的选择
            # finger_distance =math.sqrt( (middle_finger_x - index_finger_x)**2 + (middle_finger_y-index_finger_y)**2)
            finger_distance = math.hypot((middle_finger_x - index_finger_x),(middle_finger_y - index_finger_y))
 
            # 25看一下距离
            # print(finger_distance)
 
            # 26把食指指尖画出来
            cv2.circle(frame,(index_finger_x,index_finger_y),5,(0,0,255),3)
            cv2.circle(frame,(middle_finger_x,middle_finger_y),8,(0,255,255),-1)
            
            # 27判断食指与中指距离是否小于60
            if finger_distance < 60:
                # 28判断食指指尖在不在方块上
                # X坐标范围 Y坐标范围
                if (index_finger_x > square_x and index_finger_x < (square_x+w)) \
                    and (index_finger_y > square_y and index_finger_y < (square_y+h)):

                    if on_square == False:    
                        # print('在')
                        # 29食指与矩形左上角的距离
                        L1 = index_finger_x - square_x
                        L2 = index_finger_y - square_y
                        square_color = (255,0,255)
                        on_square = True
                else:
                    # print('不在')
                    ...
 
            else:
                # 解除
                on_square = False
                square_color = (0,255,0)
 
 
            # 30更新矩形的左上角坐标
            if on_square:
                square_x = index_finger_x - L1
                square_y = index_finger_y - L2
 
 
     
    # 画一个正方形，需要实心
    # cv2.rectangle(frame,(square_x,square_y),(square_x+w,square_y+h),(0,255,0),-1)
 
    # 半透明处理
    overlay = frame.copy()
    # 9 画一个方块 arg1：图像 arg2、3：坐标 arg4：颜色 arg5：画线的宽度 -1为实心
    cv2.rectangle(frame,(square_x,square_y),(square_x+w,square_y+h),square_color,-1)
    # 这是OpenCV中的一个函数，用于对两个图像进行加权叠加。
    # 参数:
    # overlay: 第一个输入图像（或称为叠加层）
    # 0.5: 这是overlay图像的权重。在这个例子中，它的权重是0.5，意味着它的贡献将是总叠加的一半
    # frame: 第二个输入图像（或称为背景图像）
    # 1 - 0.5: 我们已经知道overlay的权重是0.5，所以frame的权重是1减去0.5 frame和overlay都有相同的权重
    # 0: 这是可选的gamma值，用于调整输出图像的亮度。在这个例子中，它被设置为0，意味着不进行任何亮度调整
    frame = cv2.addWeighted(overlay, 0.5, frame, 1 - 0.5, 0)
    
    # 7 显示画面
    cv2.imshow('demo',frame)
    # 8 退出条件
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break

# 9 资源释放回收
cap.release()
cv2.destroyAllWindows()